package com.ryan.ohos.extension.event.impl;

import com.ryan.ohos.extension.event.DispatchHelper;
import com.ryan.ohos.extension.event.interfaces.View;
import com.ryan.ohos.extension.event.interfaces.ViewGroup;
import com.ryan.ohos.extension.compact.TouchEventCompact;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.multimodalinput.event.TouchEvent;

import java.util.Arrays;

/**
 * {@link ViewGroup}的实现类，所有实现逻辑均转发至此类。
 */
public class ViewGroupHelper {

    private final ViewHelper superImpl;

    // First touch target in the linked list of touch targets.
    private TouchTarget mFirstTouchTarget;
    private boolean disallowIntercept = false;

    // Lazily-created holder for point computations.
    private float[] mTempPosition;


    private final ComponentContainer container;
    private final TouchEventCompact compact;

    public ViewGroupHelper(ComponentContainer delegate) {
        this.container = delegate;
        superImpl = new ViewHelper(delegate);
        compact = DispatchHelper.getTouchEventCompact();
    }

    public TouchEventCompact getTouchEventCompact() {
        return compact;
    }

    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        if (this.disallowIntercept != disallowIntercept) {
            this.disallowIntercept = disallowIntercept;
            if (container.getComponentParent() instanceof ViewGroup) {
                ((ViewGroup) container.getComponentParent()).requestDisallowInterceptTouchEvent(disallowIntercept);
            }
        }
    }

    public boolean dispatchTouchEvent(TouchEvent ev) {
        boolean handled = false;
        final int action = ev.getAction();

        // Handle an initial down.
        if (action == TouchEvent.PRIMARY_POINT_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // Check for interception.
        final boolean intercepted;
        if (action == TouchEvent.PRIMARY_POINT_DOWN
                || mFirstTouchTarget != null) {
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                // TODO ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

        // Check for cancellation.
        final boolean canceled = resetCancelNextUpFlag(container)
                || action == TouchEvent.CANCEL;

        // TODO final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        TouchTarget newTouchTarget = null;
        final boolean split = true; // TODO (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && !isMouseEvent;
        boolean alreadyDispatchedToNewTouchTarget = false;

        if (!canceled && !intercepted) {
            if (action == TouchEvent.PRIMARY_POINT_DOWN
                    || (action == TouchEvent.OTHER_POINT_DOWN)) { // split &&
                final int actionIndex = ev.getIndex(); // always 0 for down
                // split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS
                final int idBitsToAssign = 1 << ev.getPointerId(actionIndex);

                removePointersFromTouchTargets(idBitsToAssign);
                final int childrenCount = container.getChildCount();
                if (childrenCount != 0) {
                    final float x = ev.getPointerScreenPosition(ev.getIndex()).getX();
                    final float y = ev.getPointerScreenPosition(ev.getIndex()).getY();

                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final Component child = container.getComponentAt(i);
                        if (!isTransformedTouchPointInView(x, y, child)) {
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // Did not find a child to receive the event.
                    // Assign the pointer to the least recently added target.
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it.  Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled || action == TouchEvent.PRIMARY_POINT_UP) {
            resetTouchState();
        } else if (action == TouchEvent.OTHER_POINT_UP) { // split &&
            final int actionIndex = ev.getIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }

        return handled;
    }

    public boolean onInterceptTouchEvent(TouchEvent ev) {
        if (container instanceof ViewGroup) return ((ViewGroup) container).onInterceptTouchEvent(ev);
        return false;
    }

    public boolean onTouchEvent(TouchEvent ev) {
        return superImpl.onTouchEvent(ev);
    }

    public boolean isConsumed() {
        return superImpl.isConsumed();
    }

    public void transformPointToViewLocal(float[] point, Component child) {
        point[0] += -child.getLeft();
        point[1] += -child.getTop();
    }


    /**
     * check if child rect contains the point
     * @param x x
     * @param y y
     * @param child child
     * @return if child rect contains the point
     */
    protected boolean isTransformedTouchPointInView(float x, float y, Component child) {
        if (child == null) return false;
        if (!isViewVisible(child)) return false;

        final float[] point = getTempLocationF();
        point[0] = x;
        point[1] = y;

//        transformPointToViewLocal(point, child);
        int[] location = child.getLocationOnScreen();

        int left = location[0];
        int top = location[1];

        int right = left + child.getEstimatedWidth();
        int bottom = top + child.getEstimatedHeight();

        return y >= top && y <= bottom && x >= left
                && x <= right;
    }

    /**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     * @param event the touch event
     * @param cancel is cancel event
     * @param child child to dispatch
     * @param desiredPointerIdBits desiredPointerIdBits
     * @return event is handled
     */
    private boolean dispatchTransformedTouchEvent(TouchEvent event, boolean cancel,
                                                  Component child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == TouchEvent.CANCEL) {
            compact.motionEventSetAction(event, compact.getOriginActionCancel(event));
            if (child == null) {
                handled = superImpl.dispatchTouchEvent(event);
            } else {
                handled = dispatchToChild(event, child);
            }
            compact.motionEventSetAction(event, oldAction);
            return handled;
        }

        if (child == null) {
            handled = superImpl.dispatchTouchEvent(event);
        } else {
            handled = dispatchToChild(event, child);
        }
        return handled;
    }

    private float[] getTempLocationF() {
        if (mTempPosition == null) {
            mTempPosition = new float[2];
        }
        return mTempPosition;
    }

    private boolean dispatchToChild(TouchEvent event, Component child) {
        if (child instanceof View) {
            return ((View) child).dispatchTouchEvent(event);
        }

        View view = findTouchableChild(child, event);
        if (view != null) {
            return view.dispatchTouchEvent(event);
        }
        // 由于鸿蒙是冒泡事件，所以不要尝试调用子控件Component.TouchEventListener中的onTouchEvent
//        if (child instanceof Component.TouchEventListener) ((Component.TouchEventListener) child).onTouchEvent(child, event);
        return false;
    }

    private View findTouchableChild(Component component, TouchEvent ev) {
        if (component instanceof ComponentContainer) {
            ComponentContainer container = (ComponentContainer) component;
            final int childrenCount = container.getChildCount();
            if (childrenCount != 0) {
                final float x = ev.getPointerScreenPosition(ev.getIndex()).getX();
                final float y = ev.getPointerScreenPosition(ev.getIndex()).getY();

                for (int i = childrenCount - 1; i >= 0; i--) {
                    final Component child = container.getComponentAt(i);
                    if (!isTransformedTouchPointInView(x, y, child)) {
                        continue;
                    }

                    if (child instanceof View) {
                        return (View) child;
                    } else {
                        return findTouchableChild(child, ev);
                    }
                }
            }
        }
        return null;
    }

    private boolean isViewVisible(Component view) {
        boolean visible = view.getVisibility() == Component.VISIBLE;
        if (view.getComponentParent() != null) {
            visible &= isViewVisible((Component) view.getComponentParent());
        }
        return visible;
    }

    /**
     * Cancels and clears all touch targets.
     * @param event touch event
     */
    private void cancelAndClearTouchTargets(TouchEvent event) {
        if (mFirstTouchTarget != null) {
            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                resetCancelNextUpFlag(target.child);
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            clearTouchTargets();
        }
    }

    /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(container);
        disallowIntercept = false;
    }

    /**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

    /**
     * Gets the touch target for specified child view.
     * @param child child component
     * @return null if not found.
     */
    private TouchTarget getTouchTarget(Component child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }

    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     * @param child child component
     * @param pointerIdBits pointerIdBits
     * @return TouchTarget
     */
    private TouchTarget addTouchTarget(Component child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

    /**
     * Removes the pointer ids from consideration.
     * @param pointerIdBits pointerIdBits
     */
    private void removePointersFromTouchTargets(int pointerIdBits) {
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            if ((target.pointerIdBits & pointerIdBits) != 0) {
                target.pointerIdBits &= ~pointerIdBits;
                if (target.pointerIdBits == 0) {
                    if (predecessor == null) {
                        mFirstTouchTarget = next;
                    } else {
                        predecessor.next = next;
                    }
                    target.recycle();
                    target = next;
                    continue;
                }
            }
            predecessor = target;
            target = next;
        }
    }

    /**
     * Resets the cancel next up flag.
     * @param view the view to reset
     * @return Returns true if the flag was previously set.
     */
    private static boolean resetCancelNextUpFlag(Component view) {
        // TODO
//        if ((view.getTag() & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
//            view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
//            return true;
//        }
        return false;
    }

    /** Describes a touched view and the ids of the pointers that it has captured.
     *
     * This code assumes that pointer ids are always in the range 0..31 such that
     * it can use a bitfield to track which pointer ids are present.
     * As it happens, the lower layers of the input dispatch pipeline also use the
     * same trick so the assumption should be safe here...
     */
    private static final class TouchTarget {
        private static final int MAX_RECYCLED = 32;
        private static final Object sRecycleLock = new Object[0];
        private static TouchTarget sRecycleBin;
        private static int sRecycledCount;

        public static final int ALL_POINTER_IDS = -1; // all ones

        // The touched child view.
        public Component child;

        // The combined bit mask of pointer ids for all pointers captured by the target.
        public int pointerIdBits;

        // The next target in the target list.
        public TouchTarget next;

        private TouchTarget() {
        }

        public static TouchTarget obtain(Component child, int pointerIdBits) {
            if (child == null) {
                throw new IllegalArgumentException("child must be non-null");
            }

            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) {
                    target = new TouchTarget();
                } else {
                    target = sRecycleBin;
                    sRecycleBin = target.next;
                    sRecycledCount--;
                    target.next = null;
                }
            }
            target.child = child;
            target.pointerIdBits = pointerIdBits;
            return target;
        }

        public void recycle() {
            if (child == null) {
                throw new IllegalStateException("already recycled once");
            }

            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin;
                    sRecycleBin = this;
                    sRecycledCount += 1;
                } else {
                    next = null;
                }
                child = null;
            }
        }
    }
}
